package session;

import core.Config;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;

public class ServerSession extends Session {
    public static ConcurrentLinkedQueue<SocketChannel> sockets = new ConcurrentLinkedQueue<>();
    private static ServerSocketChannel serverSocketChannel;
    private static Selector selector;

    public ServerSession(Config config) throws IOException {
        super(config);
        selector = Selector.open();

        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(config.getHost(), config.getPort()));

        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    @Override
    public void close() throws Exception {
        serverSocketChannel.close();
    }

    @Override
    public void run() {
        new Thread(() -> {
            while (true) {
                try {
                    selector.select(); // blocking

                    Iterator<SelectionKey> keysIterator = selector.selectedKeys().iterator();
                    while (keysIterator.hasNext()) {
                        SelectionKey key = keysIterator.next();

                        if (key.isAcceptable()) {
                            accept(selector, key);
                        }
                        if (key.isReadable()) {
                            keysIterator.remove();
                            read(selector, key);
                        }
                        if (key.isWritable()) {
                            keysIterator.remove();
                            write(selector, key);
                        }
                    }
                } catch (Exception ignore) {
                }
            }
        }).start();
    }

    private void accept(Selector selector, SelectionKey key) throws IOException {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
        SocketChannel socketChannel = serverSocketChannel.accept(); // can be non-blocking
        if (socketChannel != null) {
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);

            sockets.add(socketChannel);
        }
    }

    private void read(Selector selector, SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        try {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            socketChannel.read(buffer); // can be non-blocking

            buffer.flip();
            socketChannel.register(selector, SelectionKey.OP_WRITE, buffer);
        } catch (IOException e) {
            sockets.remove(socketChannel);
            socketChannel.close();
        }
    }

    private void write(Selector selector, SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        try {
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            String msg = socketChannel.socket().getPort() + ": ";
            msg += StandardCharsets.UTF_8.decode(buffer).toString();
            System.out.println(msg);

            for (SocketChannel socket : sockets) {
                socket.write(ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8))); // can be non-blocking
            }

            socketChannel.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            sockets.remove(socketChannel);
            socketChannel.close();
        }
    }
}
